﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Audio;

using OpenTK.Audio.OpenAL;

using Tunneler.Tools;

namespace Tunneler {

    enum PlayerType { Red, Blue }

    class Player : IGameObject {
        World world = World.Instance;

        SoundEffect laserHit;
        SoundEffect laserFired;
        SoundEffect tankExplosion;
        SoundEffect pickup;

        bool firstTexture;
        Cooldown textureChangeCD;

        // Two textures for animated movement
        Texture2D texture_1;
        Texture2D texture_2;

        Color[] textureData;
        Vector2 origin;

        Controls controls;
        PlayerType playerType;

        Vector2 position;
        Vector2 velocity;
        Vector2 previousPosition;
        float rotation;

        public Vector2 SpawnPosition { get; set; }
        public Vector2 Position { get { return position; } }
        public float Rotation { get { return rotation; } }

        public Texture2D Texture { get { return texture_1; } }
        public Color[] TextureData { get { return textureData; } }
        public float TextureScale { get { return 2f; } }
        public Vector2 Origin { get { return origin; } }

        #region GamePlay Data
        PlayerBase playerBase;

        PowerUp powerUp;

        Cooldown weaponCD;
        Cooldown shieldRegenCD;
        Cooldown hitCD;
        Cooldown energyDeplCD;
        Cooldown baseRegCD;

        const int MAX_HEALTH = 100;
        const int MAX_SHIELD = 100;
        const int MAX_ENERGY = 100;
        const float BASE_SPEED = 4f;

        int shieldRegVal;
        int energyCost;

        int health;
        int energy;
        int shield;

        int startingLives = 5;
        int remainingLives;

        #region Power Up Variables
        float speedFactor;
        bool adjustedFireRate;
        Cooldown fireRate;
        double weaponCDFactor;
        bool attackDisabled;
        #endregion

        #endregion
        public int Health { get { return health; } }
        public int Energy { get { return energy; } }
        public int Shield { get { return shield; } }
        public PowerUp PowerUp { get { return powerUp; } }
        public int StartingLives { get { return startingLives; } set { startingLives = value; } }
        public int RemainingLives { get { return remainingLives; } set { remainingLives = value; } }

        public Player(PlayerType playerType, PlayerBase playerBase, Controls playerControls) {
            this.controls = playerControls;
            this.playerType = playerType;
            this.playerBase = playerBase;

            InitGameData();
        }

        public void LoadContent(ContentManager content) {
            if (playerType == PlayerType.Blue) {
                texture_1 = content.Load<Texture2D>("blue_right1");
                texture_2 = content.Load<Texture2D>("blue_right2");
            } else if (playerType == PlayerType.Red) {
                texture_1 = content.Load<Texture2D>("red_right1");
                texture_2 = content.Load<Texture2D>("red_right2");
            }
            laserHit = content.Load<SoundEffect>("laserHit");
            laserFired = content.Load<SoundEffect>("laserFired");
            tankExplosion = content.Load<SoundEffect>("tankExplosion");
            pickup = content.Load<SoundEffect>("pickup");
            origin = new Vector2(texture_1.Width / 2, texture_1.Height / 2);

            // Load static texture for laser shot
            new LaserShot().LoadContent(content);

            textureData = new Color[texture_1.Width * texture_1.Height];
            texture_1.GetData<Color>(textureData);
        }

        public void Update(GameTime gameTime) {
            if (weaponCD == null) {
                weaponCD = new Cooldown(gameTime, 300);
                textureChangeCD = new Cooldown(gameTime, 20);
                hitCD = new Cooldown(gameTime, 5000);
                shieldRegenCD = new Cooldown(gameTime, 1000);
                energyDeplCD = new Cooldown(gameTime, 1000);
                baseRegCD = new Cooldown(gameTime, 1000);
            }

            UpdatePowerUp(gameTime);
            UpdatePlayer(gameTime);
        }

        public void Draw(SpriteBatch spriteBatch) {
            Texture2D drawnTexture = firstTexture ? texture_1 : texture_2;
            spriteBatch.Draw(drawnTexture, Position, null, Color.White, Rotation, Origin, TextureScale, SpriteEffects.None, 0);
        }

        public void TouchedBy(IGameObject other) {
            if (other is Player) {
                position = previousPosition;
            } else if (other is LaserShot) {
                laserHit.Play();
                TakeLaserDamage();
            } else if (other is PlayerBase) {
                position = previousPosition;
            } else if (other is Artefact) {
                RemovePowerUpEffects();
                powerUp = PowerUpGenerator.RandomPowerUp();
                pickup.Play();
            }
        }

        public void TouchedBy(Terrain terrain) {
            // No Interaction
        }

        public void Reset() {
            InitGameData();
        }

        private void InitGameData() {
            #region Initialize Stats
            health = MAX_HEALTH;
            shield = MAX_SHIELD;
            energy = MAX_ENERGY;
            remainingLives = startingLives;
            #endregion

            #region Initialize
            shieldRegVal = 10;
            energyCost = 2;
            speedFactor = 1;
            RemovePowerUpEffects();
            powerUp = null;
            #endregion

            position = SpawnPosition;
        }

        private Boolean IsDead() {
            return health <= 0 || energy <= 0;
        }

        private void UpdatePowerUp(GameTime gameTime) {
            if (powerUp == null) {
                return;
            }
            if (powerUp.Activated) {
                if (powerUp.Finnished()) {
                    // Return to the previous state and delete powerup
                    RemovePowerUpEffects();

                    powerUp = null;
                }
            } else {
                powerUp.Activate(gameTime);
                // Get type of powerup, retrieve cooldown
                AddPowerUpEffects();
            }
        }

        private void UpdatePlayer(GameTime gameTime) {
            KeyboardState keyState = Keyboard.GetState();
            GamePadState gamepadState = GamePad.GetState(controls.GamepadIndex);

            if (hitCD.IsCool()) {
                if (shieldRegenCD.IsCool()) {
                    shield = Math.Min(MAX_SHIELD, shield + shieldRegVal);
                    shieldRegenCD.Use();
                }
            }

            if (IsOutsideOfBase()) {
                if (energyDeplCD.IsCool()) {
                    energy -= energyCost;
                    energyDeplCD.Use();
                }
            } else {
                if (baseRegCD.IsCool()) {
                    health = Math.Min(MAX_HEALTH, health + 10);
                    energy = Math.Min(MAX_ENERGY, energy + 20);
                    baseRegCD.Use();
                }
            }

            velocity.X = 0;
            velocity.Y = 0;

            #region Calculating Velocity
            bool u, d, l, r;
            u = d = l = r = false;

            if (keyState.IsKeyDown(controls.Up) || gamepadState.ThumbSticks.Left.Y > 0.15 || gamepadState.DPad.Up == ButtonState.Pressed) {
                velocity.Y -= 1;
                u = true;
            }
            if (keyState.IsKeyDown(controls.Down) || gamepadState.ThumbSticks.Left.Y < -0.15 || gamepadState.DPad.Down == ButtonState.Pressed) {
                velocity.Y += 1;
                d = true;
            }
            if (keyState.IsKeyDown(controls.Left) || gamepadState.ThumbSticks.Left.X < -0.15 || gamepadState.DPad.Left == ButtonState.Pressed) {
                velocity.X -= 1;
                l = true;
            }
            if (keyState.IsKeyDown(controls.Right) || gamepadState.ThumbSticks.Left.X > 0.15 || gamepadState.DPad.Right == ButtonState.Pressed) {
                velocity.X += 1;
                r = true;
            }
            if (velocity != Vector2.Zero) {
                velocity.Normalize();
            }
            velocity *= BASE_SPEED * speedFactor;
            #endregion

            #region Calculating Rotation
            if (d & l) {
                rotation = (float)Math.PI / 2 + (float)Math.PI / 4;
            } else if (d & r) {
                rotation = (float)Math.PI / 4;
            } else if (u & l) {
                rotation = (float)Math.PI + (float)Math.PI / 4;
            } else if (u & r) {
                rotation = -(float)Math.PI / 4;
            } else if (u) {
                rotation = -(float)Math.PI / 2;
            } else if (d) {
                rotation = (float)Math.PI / 2;
            } else if (r) {
                rotation = 0;
            } else if (l) {
                rotation = (float)Math.PI;
            }
            #endregion

            if (keyState.IsKeyDown(controls.Fire) || gamepadState.Buttons.A == ButtonState.Pressed) {
                if (!attackDisabled) {
                    Shoot(gameTime);
                }
            }

            previousPosition = position;
            position += velocity;

            position.X = Math.Max(texture_1.Width / 2 * TextureScale, position.X);
            position.X = Math.Min(position.X, world.Terrain.Width - texture_1.Width / 2 * TextureScale);
            position.Y = Math.Max(texture_1.Height / 2 * TextureScale, position.Y);
            position.Y = Math.Min(position.Y, world.Terrain.Height - texture_1.Height / 2 * TextureScale);

            if (previousPosition != position) {
                if (textureChangeCD.IsCool()) {
                    firstTexture = !firstTexture;
                    textureChangeCD.Use();
                }
            }

            if (IsDead()) {
                Die();
            }
        }

        private void Die() {
            Explode();

            if (--remainingLives <= 0) {
                world.DeleteGameObject(this);
            } else {
                Respawn();
            }
        }

        private void Respawn() {
            int lives = remainingLives;
            InitGameData();
            remainingLives = lives;
        }

        private void Explode() {
            Explosion e = new Explosion(position, rotation);
            e.TextureScale = 2f;

            tankExplosion.Play();

            world.RegisterGameObject(e);
        }

        private void TakeLaserDamage() {
            hitCD.Use();
            int dmg = LaserShot.Damage;
            if (shield > 0) {
                shield -= dmg;
                if (shield < 0) {
                    dmg = Math.Abs(shield);
                    shield = 0;
                } else {
                    dmg = 0;
                }
            }
            if (dmg > 0) {
                health -= dmg;
            }
        }

        private void Shoot(GameTime gameTime) {
            if (adjustedFireRate) {
                if (fireRate == null) {
                    fireRate = new Cooldown(gameTime, weaponCD.Length * weaponCDFactor);
                }
                if (fireRate.IsHot()) {
                    return;
                }
                fireRate.Use();
            } else if (weaponCD.IsHot()) {
                return;
            }
            weaponCD.Use();

            LaserShot shot = new LaserShot(world, position);
            shot.Velocity = new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * Math.Max(velocity.Length(), 8);
            Vector2 movement = velocity;
            if (position == previousPosition) {
                movement = Vector2.Zero;
            }
            shot.Position = position + movement +
                new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * (texture_1.Width * 3 / 4);
            shot.Rotation = rotation;

            world.RegisterGameObject(shot);
            laserFired.Play();
        }

        private bool IsOutsideOfBase() {
            Rectangle bRec = new Rectangle((int)(playerBase.Position.X - playerBase.Origin.X),
                                            (int)(playerBase.Position.Y - playerBase.Origin.Y),
                (int)(playerBase.Texture.Width * playerBase.TextureScale), (int)(playerBase.Texture.Height * playerBase.TextureScale));
            Rectangle pRec = new Rectangle((int)(position.X - origin.X), (int)(position.Y - origin.Y),
                (int)(texture_1.Width * TextureScale), (int)(texture_1.Height * TextureScale));

            return !bRec.Contains(pRec);
        }

        // ==============
        // Power Up Logic
        // ==============

        #region Adding PowerUp Effects
        private void AddPowerUpEffects() {
            if (powerUp is Heal) {
                AddEffects((Heal)powerUp);
            } else if (powerUp is Energy) {
                AddEffects((Energy)powerUp);
            } else if (powerUp is Haste) {
                AddEffects((Haste)powerUp);
            } else if (powerUp is Slow) {
                AddEffects((Slow)powerUp);
            } else if (powerUp is RapidFire) {
                AddEffects((RapidFire)powerUp);
            } else if (powerUp is Disable) {
                AddEffects((Disable)powerUp);
            }
        }

        private void AddEffects(Heal heal) {
            this.health = MAX_HEALTH;
        }

        private void AddEffects(Energy energy) {
            this.energy = MAX_ENERGY;
        }

        private void AddEffects(Haste haste) {
            speedFactor = haste.SpeedFactor;
        }

        private void AddEffects(Slow slow) {
            speedFactor = slow.SpeedFactor;
        }

        private void AddEffects(RapidFire rapidFire) {
            adjustedFireRate = true;
            weaponCDFactor = rapidFire.WeaponCDFactor;
        }

        private void AddEffects(Disable disable) {
            attackDisabled = true;
        }
        #endregion

        #region Removing PowerUp Effects
        private void RemovePowerUpEffects() {
            if (powerUp == null) {
                return;
            }
            if (powerUp is Haste) {
                RemoveEffect((Haste)powerUp);
            } else if (powerUp is Slow) {
                RemoveEffect((Slow)powerUp);
            } else if (powerUp is RapidFire) {
                RemoveEffect((RapidFire)powerUp);
            } else if (powerUp is Disable) {
                RemoveEffect((Disable)powerUp);
            }
        }

        private void RemoveEffect(Haste haste) {
            speedFactor = 1;
        }

        private void RemoveEffect(Slow slow) {
            speedFactor = 1;
        }

        private void RemoveEffect(RapidFire rapidFire) {
            adjustedFireRate = false;
            weaponCDFactor = 1;
        }

        private void RemoveEffect(Disable disable) {
            attackDisabled = false;
        }
        #endregion
    }
}
